Skip to content

feat(rum): add sessionReplayOnErrorSampleRate - #24

Open
Fiona2016 wants to merge 7 commits into
mainfrom
feat/error-session-replay-sampling
Open

feat(rum): add sessionReplayOnErrorSampleRate#24
Fiona2016 wants to merge 7 commits into
mainfrom
feat/error-session-replay-sampling

Conversation

@Fiona2016

Copy link
Copy Markdown
Collaborator

Motivation

A session is stored, and charged for, as a whole. That makes "keep every error, sample everything else" awkward to express by filtering events: dropping individual events still leaves the session stored, so the thing that actually counts does not go down.

sessionReplayOnErrorSampleRate expresses it where it counts. A session drawn by this rate records a replay from the start but uploads nothing. If it never reports an error, nothing is ever sent and the session is never stored. On the first error the withheld recording is released, so the replay covers what led up to the error rather than starting at it.

Changes

  • New init option sessionReplayOnErrorSampleRate, drawn only for sessions the plain sessionReplaySampleRate draw missed, so a session is never counted by both rates. It adds one tracking type and a BUFFERED_ON_ERROR replay state.
  • startSessionReplayRecordingManually now defaults to false when either replay rate is set: a session that has to be recording before the error cannot wait to be started by hand.
  • Segment collection withholds segments while the session has not errored. The five second rotation keeps the segment growing instead of cutting it, and a buffer spanning more than a minute is dropped and restarted from a fresh full snapshot — so what is eventually released stays a minute at most and can be played from its own start.
  • The existing segment byte limit still applies while withheld, and restarts are spaced out, so a document whose full snapshot alone exceeds that limit degrades instead of restarting in a loop.
  • A withheld buffer belongs to the session that produced it. It is released only when that same session reports the error; if the session expires or is renewed first the records are dropped, so an expiry can never turn into an upload for a session that never errored. A page being merely hidden keeps the buffer, since it comes back and the full snapshot would otherwise be lost with it.
  • Dropped buffers roll their replay stats back, and neither has_replay nor the replay link is offered while a replay is withheld, so nothing points at a replay that does not exist.
  • Errors the SDK raises about its own transport (error.source === 'agent') release nothing: an unreachable intake should not turn every session into an error session.
  • Telemetry on release reports how much history the buffer actually carried, so the window can be checked against the one it promises.

Also included: the profiling test helpers imported from package names this repository does not publish. Because the helper is re-exported from the rum test barrel, those imports stopped roughly 220 specs from loading at all; fixing them brings the suite back.

Test instructions

yarn test:unit. New specs cover withholding, release, the checkout window, the byte limit, the stats rollback, hidden pages, and session expiry and renewal.

By hand, against yarn dev:

  1. init with sessionSampleRate: 100, sessionReplaySampleRate: 0, sessionReplayOnErrorSampleRate: 100, then call startSessionReplayRecording().
  2. Interact with the page for a while and watch the network panel: no replay request is made.
  3. Report an error. One replay request follows, and its segment metadata has has_full_snapshot: true with records reaching back before the error.
  4. Repeat with the session running past a minute before the error: the released segment reaches back at most a minute, not the whole session.

Driven through Playwright against the same page, a 108 second session uploaded nothing before the error and then released a segment reaching back 48 seconds, with the full snapshot present.

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.

…export from

mockProfiler imported registerCleanupTask and getGlobalObject from the rum
package rather than core, and profiler.spec.ts imported from package names this
repository does not publish. Since mockProfiler is re-exported from the rum test
barrel, the broken imports took every spec that touches that barrel down with
them - around 220 tests never ran.
A session drawn by this rate records from the start but uploads nothing until it
reports an error. If none ever happens, nothing is sent and the session is never
stored. On the first error the withheld buffer is released and recording
continues normally, so the replay covers what led up to the error rather than
starting at it.

The buffer is bounded on both axes. Time: a buffer that spans more than a minute
is dropped and restarted from a fresh full snapshot, so what is released stays a
minute at most. Size: the existing segment byte limit still applies while
withheld, and restarts are spaced out so that a document whose full snapshot
alone exceeds that limit degrades instead of restarting in a loop.

A withheld buffer belongs to the session that produced it. It is released only
when that same session reports the error - if the session expires or is renewed
first, the records are dropped, so an expiry can never turn into an upload for a
session that never errored. Buffers that are dropped roll back their replay
stats, and has_replay is not reported while a replay is being withheld, so
neither the counters nor the link offer a replay that does not exist.

Errors raised by the SDK about its own transport do not release anything: those
are our failures, not the application's, and counting them would make every
session an error session wherever our endpoint is unreachable.
The flush path derived the same thing twice under two names, and the mapping of
the internal checkout reason onto a schema value only ever had one caller.
A page-exit rotation used to throw the buffer away, and with it the full snapshot
a released replay has to start from - everything recorded afterwards is
incremental and cannot be played on its own. Switching tabs raises this exit, and
the page comes straight back, so an error reported after that would have released
a replay that renders as good as nothing until the next view.

Nothing can be sent while withheld, so there was never anything to gain from the
rotation. A page that is really unloading takes the buffer with it either way.
…ased buffer

Only the rotation notices that the withheld replay has been released, so a
session that expires within one rotation of its own error still loses what the
error had earned. Closing it would mean asking the session manager on every
record.
The rollback that gives a dropped buffer's index_in_view back only lands when the
encoder finishes, which is always a turn later. Restarting from a fresh full
snapshot emitted records right away, so the next segment took its index before
the rollback arrived - and once that session errored, two uploaded segments
claimed the same index within one view while nothing claimed the first. Any error
session that spends a minute on one view before erroring hit it.

The restart now happens where the rollback lands.

Also corrects a comment: a session expiring right after its own error does not
lose the buffer. The history entry is still open when the recorder is stopped, so
the stop flush sees the session as released and sends.
Without it, a replay collected under this rate is indistinguishable from one
collected unconditionally once it has been uploaded - the two cost differently
and answer different questions, and nothing downstream could tell them apart.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant